μ½ν μΈ λ³΄μ μ μ± (CSP) λ° κΈ°ν νλ‘ νΈμλ 보μ ν€λμ λν ν¬κ΄μ μΈ κ°μ΄λλ‘, μΉ μ ν리μΌμ΄μ μ 곡격μΌλ‘λΆν° 보νΈνκ³ μ μΈκ³ μ¬μ©μ 보μμ κ°νν©λλ€.
νλ‘ νΈμλ 보μ ν€λ: μ½ν μΈ λ³΄μ μ μ± (CSP) λ§μ€ν°νκΈ°
μΉ μ ν리μΌμ΄μ μ΄ μ μ λ 볡μ‘ν΄μ§κ³ μνΈ μ°κ²°λλ μ€λλ μ λμ§νΈ νκ²½μμλ 보μ μνμΌλ‘λΆν° 보νΈνλ κ²μ΄ 무μλ³΄λ€ μ€μν©λλ€. λ°±μλ 보μμ΄ μ’ μ’ μλΉν μ£Όλͺ©μ λ°μ§λ§, νλ‘ νΈμλ 보μ μμ λ§μ°¬κ°μ§λ‘ μ€μν©λλ€. νλ‘ νΈμλ 보μ ν€λλ 첫 λ²μ§Έ λ°©μ΄μ μν μ νλ©°, λΈλΌμ°μ μ μ΄λ»κ² λμν΄μΌ νλμ§ μ§μνκ³ λ€μν 곡격μΌλ‘λΆν° μ¬μ©μλ₯Ό 보νΈνλ λ©μ»€λμ¦μ μ 곡ν©λλ€. μ΄λ¬ν ν€λ μ€μμ μ½ν μΈ λ³΄μ μ μ± (CSP)μ κ΄λ²μν μνμ μννλ κ°λ ₯ν λκ΅¬λ‘ λ보μ λλ€.
νλ‘ νΈμλ 보μ ν€λλ 무μμΈκ°?
νλ‘ νΈμλ 보μ ν€λλ μΉ μλ²κ° λΈλΌμ°μ λ‘ λ³΄λ΄λ HTTP μλ΅ ν€λμ λλ€. μ΄ ν€λμλ λΈλΌμ°μ κ° μμ νλ μ½ν μΈ λ₯Ό μ²λ¦¬νλ λ°©λ²μ λν μ§μΉ¨μ΄ ν¬ν¨λμ΄ μμ΅λλ€. λ€μκ³Ό κ°μ μΌλ°μ μΈ κ³΅κ²©μ λ°©μ§νλ λ° λμμ΄ λ©λλ€:
- ν¬λ‘μ€ μ¬μ΄νΈ μ€ν¬λ¦½ν (XSS): μ λ’°ν μ μλ μΉμ¬μ΄νΈμ μ μ± μ€ν¬λ¦½νΈλ₯Ό μ£Όμ νλ 곡격.
- ν΄λ¦μ¬νΉ: μ¬μ©μκ° μΈμνλ κ²κ³Ό λ€λ₯Έ κ²μ ν΄λ¦νλλ‘ μμ΄λ 곡격.
- μ€κ°μ 곡격(Man-in-the-Middle Attacks): μ¬μ©μμ μλ² κ°μ ν΅μ μ κ°λ‘μ±λ 곡격.
κ°μ₯ μ€μν νλ‘ νΈμλ 보μ ν€λλ λ€μκ³Ό κ°μ΅λλ€:
- μ½ν μΈ λ³΄μ μ μ± (CSP): λΈλΌμ°μ κ° λ¦¬μμ€λ₯Ό λ‘λν μ μλ μμ€λ₯Ό μ μν©λλ€.
- Strict-Transport-Security (HSTS): λΈλΌμ°μ κ° μΉμ¬μ΄νΈμμ λͺ¨λ ν΅μ μ HTTPSλ₯Ό μ¬μ©νλλ‘ κ°μ ν©λλ€.
- X-Frame-Options: μΉμ¬μ΄νΈκ° iframeμ μλ² λλλ κ²μ λ°©μ§νμ¬ ν΄λ¦μ¬νΉ 곡격μ μνν©λλ€.
- X-XSS-Protection: λΈλΌμ°μ μ λ΄μ₯ XSS νν°λ₯Ό νμ±νν©λλ€. (μ°Έκ³ : μ’ μ’ CSPλ‘ λ체λμ§λ§ μ¬μ ν λ°©μ΄ κ³μΈ΅μ μ 곡ν μ μμ΅λλ€).
- Referrer-Policy: μμ²κ³Ό ν¨κ» μ μ‘λλ 리νΌλ¬ μ 보μ μμ μ μ΄ν©λλ€.
- Feature-Policy (νμ¬ Permissions-Policy): κ°λ°μκ° λΈλΌμ°μ κΈ°λ₯ λ° APIλ₯Ό μ νμ μΌλ‘ νμ±ν λ° λΉνμ±νν μ μλλ‘ ν©λλ€.
μ½ν μΈ λ³΄μ μ μ± (CSP) μ¬μΈ΅ λΆμ
μ½ν μΈ λ³΄μ μ μ± (CSP)μ μ¬μ©μ μμ΄μ νΈκ° νΉμ νμ΄μ§μ λν΄ λ‘λν μ μλ 리μμ€λ₯Ό μ μ΄νλ HTTP μλ΅ ν€λμ λλ€. μ΄λ λ³Έμ§μ μΌλ‘ μΉμΈλ μ½ν μΈ μμ€λ₯Ό νμ΄νΈλ¦¬μ€νΈμ μΆκ°νμ¬ XSS 곡격μ μνμ ν¬κ² μ€μ λλ€. μ€ν¬λ¦½νΈ, μ€νμΌμνΈ, μ΄λ―Έμ§, κΈκΌ΄κ³Ό κ°μ 리μμ€λ₯Ό λ‘λν μ μλ μΆμ²λ₯Ό λͺ μμ μΌλ‘ μ μν¨μΌλ‘μ¨ CSPλ 곡격μκ° μΉμ¬μ΄νΈμ μ μ± μ½λλ₯Ό μ£Όμ νλ κ²μ ν¨μ¬ λ μ΄λ ΅κ² λ§λλλ€.
CSP μλ λ°©μ
CSPλ λΈλΌμ°μ μ λ€μν μ νμ μ½ν μΈ μ λν μΉμΈλ μμ€ λͺ©λ‘μ μ 곡ν¨μΌλ‘μ¨ μλν©λλ€. λΈλΌμ°μ κ° CSPλ₯Ό μλ°νλ 리μμ€λ₯Ό λ§λλ©΄ ν΄λΉ 리μμ€λ₯Ό μ°¨λ¨νκ³ μλ° μ¬νμ λ³΄κ³ ν©λλ€. μ΄ μ°¨λ¨ λ©μ»€λμ¦μ 곡격μκ° HTMLμ μ μ± μ½λλ₯Ό μ£Όμ νλ λ° μ±κ³΅νλλΌλ ν΄λΉ μ½λκ° μ€νλλ κ²μ λ°©μ§ν©λλ€.
CSP μ§μλ¬Έ
CSP μ§μλ¬Έμ CSP μ μ± μ ν΅μ¬ κ΅¬μ± μμμ λλ€. μ΄λ λ€μν μ νμ 리μμ€μ νμ©λλ μμ€λ₯Ό μ§μ ν©λλ€. κ°μ₯ μΌλ°μ μΌλ‘ μ¬μ©λλ μ§μλ¬Έμ λ€μκ³Ό κ°μ΅λλ€:
- default-src: λͺ¨λ 리μμ€ μ νμ λν κΈ°λ³Έ μμ€λ₯Ό μ€μ ν©λλ€. μ΄λ λ€λ₯Έ λ ꡬ체μ μΈ μ§μλ¬Έμ΄ μ μλμ§ μμ κ²½μ° μ μ©λλ λ체 μ§μλ¬Έμ λλ€.
- script-src: JavaScriptμ νμ©λλ μμ€λ₯Ό μ§μ ν©λλ€.
- style-src: CSS μ€νμΌμνΈμ νμ©λλ μμ€λ₯Ό μ§μ ν©λλ€.
- img-src: μ΄λ―Έμ§μ νμ©λλ μμ€λ₯Ό μ§μ ν©λλ€.
- font-src: κΈκΌ΄μ νμ©λλ μμ€λ₯Ό μ§μ ν©λλ€.
- media-src: μ€λμ€ λ° λΉλμ€μ νμ©λλ μμ€λ₯Ό μ§μ ν©λλ€.
- object-src: Flashμ κ°μ νλ¬κ·ΈμΈμ νμ©λλ μμ€λ₯Ό μ§μ ν©λλ€. (κ°λ₯νλ€λ©΄ νλ¬κ·ΈμΈμ νμ©νμ§ μλ κ²μ΄ κ°μ₯ μ’μ΅λλ€).
- frame-src: νλ μ(iframe)μ νμ©λλ μμ€λ₯Ό μ§μ ν©λλ€.
- connect-src: λ€νΈμν¬ μμ²(AJAX, WebSockets)μ νμ©λλ μμ€λ₯Ό μ§μ ν©λλ€.
- base-uri:
<base>μμμμ μ¬μ©ν μ μλ URLμ μ νν©λλ€. - form-action: μμμ μ μΆν μ μλ URLμ μ νν©λλ€.
- frame-ancestors:
<frame>,<iframe>,<object>,<embed>, λλ<applet>μ μ¬μ©νμ¬ νμ΄μ§λ₯Ό μλ² λν μ μλ μ ν¨ν λΆλͺ¨λ₯Ό μ§μ ν©λλ€. μ΄ μ§μλ¬Έμ ν΄λ¦μ¬νΉμ λν 보νΈλ₯Ό μ 곡ν©λλ€. - upgrade-insecure-requests: μ¬μ©μ μμ΄μ νΈμκ² μ¬μ΄νΈμ λͺ¨λ λΉλ³΄μ URL(HTTPλ₯Ό ν΅ν΄ λ‘λλ¨)μ 보μ URL(HTTPSλ₯Ό ν΅ν΄ λ‘λλ¨)λ‘ λ체λ κ²μ²λΌ μ²λ¦¬νλλ‘ μ§μν©λλ€. μ΄ μ§μλ¬Έμ HTTPμμ HTTPSλ‘ λ§μ΄κ·Έλ μ΄μ νλ κ³Όμ μ μλ μΉμ¬μ΄νΈλ₯Ό μν κ²μ λλ€.
- report-uri: λΈλΌμ°μ κ° CSP μλ°μ λν λ³΄κ³ μλ₯Ό 보λ΄μΌ νλ URLμ μ§μ ν©λλ€. `report-to`λ₯Ό μν΄ μ¬μ©μ΄ μ€λ¨λμμ΅λλ€.
- report-to: `Report-To` ν€λμ μ μλ κ·Έλ£Ή μ΄λ¦μ μ§μ ν©λλ€. μ΄λ₯Ό ν΅ν΄ μ¬λ¬ λ³΄κ³ μλν¬μΈνΈλ₯Ό μ§μ νλ λ± λ³΄κ³ μ λν μΈλ°ν μ μ΄κ° κ°λ₯ν©λλ€.
CSP μμ€ κ°
μμ€ κ°μ 리μμ€λ₯Ό λ‘λν μ μλ μΆμ²λ₯Ό μ μν©λλ€. λͺ κ°μ§ μΌλ°μ μΈ μμ€ κ°μ λ€μκ³Ό κ°μ΅λλ€:
- *: λͺ¨λ μμ€μ μ½ν μΈ λ₯Ό νμ©ν©λλ€ (νλ‘λμ νκ²½μμλ μ¬μ©νμ§ λ§μμμ€!).
- 'self': 보νΈλ λ¬Έμμ λμΌν μΆμ²(μ€ν€λ§, νΈμ€νΈ, ν¬νΈ)μ μ½ν μΈ λ₯Ό νμ©ν©λλ€.
- 'none': μ΄λ€ μμ€μ μ½ν μΈ λ νμ©νμ§ μμ΅λλ€.
- 'unsafe-inline': μΈλΌμΈ JavaScript λ° CSS μ¬μ©μ νμ©ν©λλ€ (νλ‘λμ νκ²½μμλ μ¬μ©νμ§ λ§μμμ€!).
- 'unsafe-eval': λμ μ½λ νκ°(μ:
eval(),Function()) μ¬μ©μ νμ©ν©λλ€ (νλ‘λμ νκ²½μμλ μ¬μ©νμ§ λ§μμμ€!). - 'strict-dynamic': λ§ν¬μ μ μλ μ€ν¬λ¦½νΈμ λ Όμ€(nonce)λ ν΄μλ₯Ό λλ°νμ¬ λͺ μμ μΌλ‘ λΆμ¬λ μ λ’°κ° ν΄λΉ μ‘°μμ΄ λ‘λνλ λͺ¨λ μ€ν¬λ¦½νΈλ‘ μ νλμ΄μΌ ν¨μ μ§μ ν©λλ€.
- 'unsafe-hashes': νΉμ μΈλΌμΈ μ΄λ²€νΈ νΈλ€λ¬λ₯Ό νμ©ν©λλ€. μ΄λ 볡μ‘μ±κ³Ό μ νλ μ΄μ μΌλ‘ μΈν΄ μΌλ°μ μΌλ‘ κΆμ₯λμ§ μμ΅λλ€.
- data:: λ°μ΄ν° URL(μ: μλ² λλ μ΄λ―Έμ§)μμ 리μμ€ λ‘λλ₯Ό νμ©ν©λλ€. μ£Όμν΄μ μ¬μ©νμμμ€.
- mediastream:: `mediastream:` URIλ₯Ό λ―Έλμ΄ μμ€λ‘ μ¬μ©ν μ μλλ‘ νμ©ν©λλ€.
- blob:: `blob:` URIλ₯Ό λ―Έλμ΄ μμ€λ‘ μ¬μ©ν μ μλλ‘ νμ©ν©λλ€.
- filesystem:: νμΌ μμ€ν μμ 리μμ€λ₯Ό λ‘λν μ μλλ‘ νμ©ν©λλ€.
- https://example.com: νΉμ λλ©μΈ λ° ν¬νΈμ μ½ν μΈ λ₯Ό νμ©ν©λλ€.
- *.example.com: example.comμ λͺ¨λ νμ λλ©μΈ μ½ν μΈ λ₯Ό νμ©ν©λλ€.
- nonce-{random-value}: μΌμΉνλ λ Όμ€ μμ±μ΄ μλ μ€ν¬λ¦½νΈ λλ μ€νμΌμ νμ©ν©λλ€. μ΄λ₯Ό μν΄μλ κ° μμ²μ λν΄ μλ² μΈ‘μμ μμμ λ Όμ€ κ°μ μμ±ν΄μΌ ν©λλ€.
- sha256-{hash-value}: μΌμΉνλ SHA256, SHA384 λλ SHA512 ν΄μκ° μλ μ€ν¬λ¦½νΈ λλ μ€νμΌμ νμ©ν©λλ€.
CSP λͺ¨λ: κ°μ (Enforce) vs. λ³΄κ³ μ μ©(Report-Only)
CSPλ λ κ°μ§ λͺ¨λλ‘ λ°°ν¬ν μ μμ΅λλ€:
- κ°μ λͺ¨λ: μ΄ λͺ¨λμμ λΈλΌμ°μ λ CSPλ₯Ό μλ°νλ λͺ¨λ 리μμ€λ₯Ό μ°¨λ¨ν©λλ€. μ΄λ νλ‘λμ νκ²½μ κΆμ₯λλ λͺ¨λμ λλ€. CSPλ `Content-Security-Policy` ν€λλ₯Ό μ¬μ©νμ¬ μ μ‘λ©λλ€.
- λ³΄κ³ μ μ© λͺ¨λ: μ΄ λͺ¨λμμ λΈλΌμ°μ λ CSP μλ°μ λ³΄κ³ νμ§λ§ 리μμ€λ₯Ό μ°¨λ¨νμ§λ μμ΅λλ€. μ΄λ CSPλ₯Ό κ°μ νκΈ° μ μ ν μ€νΈνκ³ νκ°νλ λ° μ μ©ν©λλ€. CSPλ `Content-Security-Policy-Report-Only` ν€λλ₯Ό μ¬μ©νμ¬ μ μ‘λ©λλ€.
CSP ꡬν: λ¨κ³λ³ κ°μ΄λ
CSP ꡬνμ μ΄λ €μ λ³΄μΌ μ μμ§λ§, 체κ³μ μΈ μ κ·Ό λ°©μμ λ°λ₯΄λ©΄ μΉ μ ν리μΌμ΄μ μ ν¨κ³Όμ μΌλ‘ 보νΈν μ μμ΅λλ€.
1. λ³΄κ³ μ μ© μ μ± μΌλ‘ μμνκΈ°
λ³΄κ³ μ μ© λͺ¨λλ‘ CSPλ₯Ό λ°°ν¬νμ¬ μμνμμμ€. μ΄λ₯Ό ν΅ν΄ μΉμ¬μ΄νΈμ κΈ°λ₯μ μ€λ¨νμ§ μκ³ μλ° μ¬νμ λͺ¨λν°λ§ν μ μμ΅λλ€. μλ° λ³΄κ³ μλ₯Ό μ§μ λ μλν¬μΈνΈλ‘ 보λ΄λλ‘ report-uri λλ report-to μ§μλ¬Έμ ꡬμ±νμμμ€.
μμ ν€λ (λ³΄κ³ μ μ©):
Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report
2. μλ° λ³΄κ³ μ λΆμνκΈ°
μλ° λ³΄κ³ μλ₯Ό μ£Όμ κΉκ² λΆμνμ¬ μ΄λ€ 리μμ€κ° μ μ°¨λ¨λκ³ μλμ§ μλ³νμμμ€. μ΄λ μΉμ¬μ΄νΈμ 리μμ€ μμ‘΄μ±μ μ΄ν΄νκ³ μ μ¬μ μΈ λ³΄μ μ·¨μ½μ μ μλ³νλ λ° λμμ΄ λ κ²μ λλ€.
μλ° λ³΄κ³ μλ μΌλ°μ μΌλ‘ JSON νμ΄λ‘λλ‘ κ΅¬μ±λ report-uri λλ report-to μλν¬μΈνΈλ‘ μ μ‘λ©λλ€. μ΄ λ³΄κ³ μμλ μ°¨λ¨λ URI, μλ°λ μ§μλ¬Έ, λ¬Έμ URIμ κ°μ μλ°μ λν μ λ³΄κ° ν¬ν¨λ©λλ€.
3. CSP μ μ± κ΅¬μ²΄ννκΈ°
μλ° λ³΄κ³ μλ₯Ό κΈ°λ°μΌλ‘ CSP μ μ±
μ ꡬ체ννμ¬ ν©λ²μ μΈ λ¦¬μμ€λ₯Ό νμ©νλ©΄μλ κ°λ ₯ν 보μ νμΈλ₯Ό μ μ§νμμμ€. μ°¨λ¨λλ 리μμ€μ λν΄ νΉμ μμ€ κ°μ μΆκ°νμμμ€. 'unsafe-inline' μ¬μ©μ νΌνκΈ° μν΄ μΈλΌμΈ μ€ν¬λ¦½νΈ λ° μ€νμΌμ λ
Όμ€λ ν΄μλ₯Ό μ¬μ©νλ κ²μ κ³ λ €νμμμ€.
4. κ°μ λͺ¨λλ‘ μ ννκΈ°
CSP μ μ± μ΄ ν©λ²μ μΈ λ¦¬μμ€λ₯Ό μ°¨λ¨νμ§ μλλ€κ³ νμ νλ©΄ κ°μ λͺ¨λλ‘ μ ννμμμ€. μ΄λ κ² νλ©΄ λ¨μμλ λͺ¨λ μλ° μ¬νμ΄ μ°¨λ¨λκ³ XSS 곡격μ λν κ°λ ₯ν 보μ κ³μΈ΅μ΄ μ 곡λ©λλ€.
μμ ν€λ (κ°μ ):
Content-Security-Policy: default-src 'self'; script-src 'self' https://example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; report-uri /csp-report
5. CSP μ μ± λͺ¨λν°λ§ λ° μ μ§ κ΄λ¦¬νκΈ°
CSPλ ν λ² μ€μ νκ³ μμ΄λ²λ¦¬λ ν΄κ²°μ± μ΄ μλλλ€. μΉμ¬μ΄νΈκ° λ°μ νκ³ μλ‘μ΄ λ³΄μ μνμ΄ λνλ¨μ λ°λΌ CSP μ μ± μ μ§μμ μΌλ‘ λͺ¨λν°λ§νκ³ μ λ°μ΄νΈνλ κ²μ΄ μ€μν©λλ€. μ κΈ°μ μΌλ‘ μλ° λ³΄κ³ μλ₯Ό κ²ν νκ³ νμμ λ°λΌ μ μ± μ μ‘°μ νμμμ€.
μ€μ©μ μΈ CSP μμ
λ€μν μλ리μ€μ λν λͺ κ°μ§ μ€μ©μ μΈ CSP μμ λ₯Ό μ΄ν΄λ³΄κ² μ΅λλ€:
μμ 1: κ°λ¨ν μΉμ¬μ΄νΈλ₯Ό μν κΈ°λ³Έ CSP
μ΄ CSPλ λμΌν μΆμ²μ μ½ν μΈ λ₯Ό νμ©νκ³ λͺ¨λ μμ€μ μ΄λ―Έμ§λ₯Ό νμ©ν©λλ€.
Content-Security-Policy: default-src 'self'; img-src *
μμ 2: νΉμ μ€ν¬λ¦½νΈ λ° μ€νμΌ μμ€λ₯Ό μ¬μ©ν CSP
μ΄ CSPλ λμΌν μΆμ²μ νΉμ CDNμ μ€ν¬λ¦½νΈλ₯Ό νμ©νκ³ , λμΌν μΆμ²μ μ€νμΌκ³Ό μΈλΌμΈ μ€νμΌμ νμ©ν©λλ€.
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'
μμ 3: μΈλΌμΈ μ€ν¬λ¦½νΈμ λ Όμ€λ₯Ό μ¬μ©ν CSP
μ΄ CSPλ κ° μΈλΌμΈ μ€ν¬λ¦½νΈμ κ³ μ ν λ Όμ€λ₯Ό μꡬν©λλ€.
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-r4nd0mn0nc3'
HTML:
<script nonce="r4nd0mn0nc3">console.log('Hello, world!');</script>
μ€μ: λ Όμ€ κ°μ κ° μμ²μ λν΄ μλ²μμ λμ μΌλ‘ μμ±λμ΄μΌ ν©λλ€. μ΄λ 곡격μκ° λ Όμ€λ₯Ό μ¬μ¬μ©νλ κ²μ λ°©μ§ν©λλ€.
μμ 4: ν΄λ¦μ¬νΉ λ°©μ§λ₯Ό μν΄ νλ μ μ‘°μμ μ ννλ CSP
μ΄ CSPλ νμ΄μ§κ° `https://example.com`μ μ μΈν λͺ¨λ λλ©μΈμ iframeμ μλ² λλλ κ²μ λ°©μ§ν©λλ€.
Content-Security-Policy: frame-ancestors 'self' https://example.com
μμ 5: 'strict-dynamic'κ³Ό 'self'λ‘μ ν΄λ°±μ μ¬μ©νλ λ μ νμ μΈ CSP
μ΄ CSPλ μ΅μ λΈλΌμ°μ λ₯Ό μν΄ `strict-dynamic`μ νμ©νλ©΄μλ μ΄λ₯Ό μ§μνμ§ μλ ꡬν λΈλΌμ°μ λ μ§μν©λλ€. λν μλ° λͺ¨λν°λ§μ μν `report-uri`λ₯Ό ν¬ν¨ν©λλ€.
Content-Security-Policy: default-src 'self'; script-src 'strict-dynamic' 'nonce-{random-nonce}' 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; report-uri /csp-report
μλ² μΈ‘μμ `{random-nonce}`λ₯Ό λμ μΌλ‘ μμ±λ λ Όμ€ κ°μΌλ‘ κ΅μ²΄νλ κ²μ μμ§ λ§μμμ€.
CSPμ λ¨μΌ νμ΄μ§ μ ν리μΌμ΄μ (SPA)
SPAμ CSPλ₯Ό ꡬννλ κ²μ μ΄λ¬ν μ ν리μΌμ΄μ μ λμ νΉμ±μΌλ‘ μΈν΄ μ΄λ €μΈ μ μμ΅λλ€. SPAλ μ’ μ’ DOMμ μμ±νκ³ μ‘°μνκΈ° μν΄ JavaScriptμ ν¬κ² μμ‘΄νλ©°, μ΄λ μ μ€νκ² μ²λ¦¬νμ§ μμΌλ©΄ CSP μλ°μΌλ‘ μ΄μ΄μ§ μ μμ΅λλ€.
SPAμ CSPλ₯Ό ꡬννκΈ° μν λͺ κ°μ§ νμ λ€μκ³Ό κ°μ΅λλ€:
'unsafe-inline'λ°'unsafe-eval'νΌνκΈ°: μ΄ μ§μλ¬Έλ€μ SPAμμ κ°λ₯ν ν νΌν΄μΌ ν©λλ€. μ΄λ μ ν리μΌμ΄μ μ 보μμ ν¬κ² μ½νμν΅λλ€.- λ Όμ€ λλ ν΄μ μ¬μ©νκΈ°: μΈλΌμΈ μ€ν¬λ¦½νΈ λ° μ€νμΌμ λ Όμ€ λλ ν΄μλ₯Ό μ¬μ©νμμμ€. μ΄κ²μ΄ SPAμ κΆμ₯λλ μ κ·Ό λ°©μμ λλ€.
- Trusted Types κ³ λ €νκΈ°: Trusted Typesλ DOM κΈ°λ° XSS μ·¨μ½μ μ λ°©μ§νλ λ° λμμ΄ λλ λΈλΌμ°μ APIμ λλ€. 보μμ λμ± κ°ννκΈ° μν΄ CSPμ ν¨κ» μ¬μ©ν μ μμ΅λλ€.
- CSP νΈν νλ μμν¬ μ¬μ©νκΈ°: μΌλΆ νλ‘ νΈμλ νλ μμν¬(νΉμ ꡬμ±μ React, Angular, Vue.js λ±)λ CSPλ₯Ό λ μ½κ² ꡬννλ λ° λμμ΄ λλ κΈ°λ₯μ μ 곡ν©λλ€.
κΈ°ν μ€μν νλ‘ νΈμλ 보μ ν€λ
CSPλ νλ‘ νΈμλ 보μμ μ΄μμ΄μ§λ§, λ€λ₯Έ ν€λλ€λ ν¬κ΄μ μΈ λ°©μ΄ μ λ΅μ μ 곡νλ λ° μ€μν μν μ ν©λλ€:
Strict-Transport-Security (HSTS)
Strict-Transport-Security (HSTS) ν€λλ λΈλΌμ°μ μκ² νμ HTTPSλ₯Ό μ¬μ©νμ¬ μΉμ¬μ΄νΈμ μ°κ²°νλλ‘ μ§μν©λλ€. μ΄λ μ°κ²°μ HTTPλ‘ λ€μ΄κ·Έλ μ΄λνλ €λ μ€κ°μ 곡격μ λ°©μ§ν©λλ€.
μμ ν€λ:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
max-age: λΈλΌμ°μ κ° μ¬μ΄νΈμ HTTPSλ₯Ό ν΅ν΄μλ§ μ μν΄μΌ νλ€λ κ²μ κΈ°μ΅ν΄μΌ ν κΈ°κ°(μ΄ λ¨μ)μ μ§μ ν©λλ€. νλ‘λμ νκ²½μμλ 31536000μ΄(1λ )μ κ°μ΄ κΆμ₯λ©λλ€.includeSubDomains: HSTS μ μ± μ΄ λλ©μΈμ λͺ¨λ νμ λλ©μΈμ μ μ©λ¨μ λνλ λλ€.preload: λλ©μΈμ΄ λΈλΌμ°μ μ 미리 λ‘λλλ HSTS νμ±ν λλ©μΈ λͺ©λ‘μ ν¬ν¨λ μ μλλ‘ νμ©ν©λλ€. μ΄λ₯Ό μν΄μλ Googleμ΄ μ μ§ κ΄λ¦¬νλ HSTS μ¬μ λ‘λ λͺ©λ‘μ λλ©μΈμ μ μΆν΄μΌ ν©λλ€.
X-Frame-Options
X-Frame-Options ν€λλ μΉμ¬μ΄νΈκ° iframeμ μλ² λλ μ μλμ§ μ¬λΆλ₯Ό μ μ΄νμ¬ ν΄λ¦μ¬νΉ 곡격μ λ°©μ§ν©λλ€.
μμ ν€λ:
X-Frame-Options: DENY
κ°λ₯ν κ°:
DENY: μΆμ²μ κ΄κ³μμ΄ νμ΄μ§κ° iframeμ νμλλ κ²μ λ°©μ§ν©λλ€.SAMEORIGIN: iframeμ μΆμ²κ° νμ΄μ§μ μΆμ²μ μΌμΉνλ κ²½μ°μλ§ νμ΄μ§λ₯Ό iframeμ νμν μ μμ΅λλ€.ALLOW-FROM uri: iframeμ μΆμ²κ° μ§μ λ URIμ μΌμΉνλ κ²½μ°μλ§ νμ΄μ§λ₯Ό iframeμ νμν μ μμ΅λλ€. μ°Έκ³ : μ΄ μ΅μ μ λ μ΄μ μ¬μ©λμ§ μμΌλ©° λͺ¨λ λΈλΌμ°μ μμ μ§μλμ§ μμ μ μμ΅λλ€.
μ°Έκ³ : CSPμ frame-ancestors μ§μλ¬Έμ νλ μ΄λ°μ μ μ΄νλ λ μ μ°νκ³ κ°λ ₯ν λ°©λ²μ μ 곡νλ©° μΌλ°μ μΌλ‘ X-Frame-Optionsλ³΄λ€ μ νΈλ©λλ€.
X-XSS-Protection
X-XSS-Protection ν€λλ λΈλΌμ°μ μ λ΄μ₯ XSS νν°λ₯Ό νμ±νν©λλ€. CSPκ° XSS 곡격μ λ°©μ§νκΈ° μν λ κ°λ ₯ν μ루μ
μ΄μ§λ§, μ΄ ν€λλ νΉν CSPλ₯Ό μμ ν μ§μνμ§ μμ μ μλ ꡬν λΈλΌμ°μ μ λν΄ μΆκ°μ μΈ λ°©μ΄ κ³μΈ΅μ μ 곡ν μ μμ΅λλ€.
μμ ν€λ:
X-XSS-Protection: 1; mode=block
1: XSS νν°λ₯Ό νμ±νν©λλ€.0: XSS νν°λ₯Ό λΉνμ±νν©λλ€.mode=block: XSS κ³΅κ²©μ΄ κ°μ§λλ©΄ νμ΄μ§λ₯Ό μ°¨λ¨νλλ‘ λΈλΌμ°μ μ μ§μν©λλ€.report=uri: XSS κ³΅κ²©μ΄ κ°μ§λλ©΄ λΈλΌμ°μ κ° λ³΄κ³ μλ₯Ό 보λ΄μΌ νλ URLμ μ§μ ν©λλ€.
Referrer-Policy
Referrer-Policy ν€λλ μμ²κ³Ό ν¨κ» μ μ‘λλ 리νΌλ¬ μ 보μ μμ μ μ΄ν©λλ€. 리νΌλ¬ μ 보λ μΉμ¬μ΄νΈ κ°μ μ¬μ©μλ₯Ό μΆμ νλ λ° μ¬μ©λ μ μμΌλ―λ‘ μ΄λ₯Ό μ μ΄νλ©΄ μ¬μ©μ κ°μΈ μ 보λ₯Ό ν₯μμν¬ μ μμ΅λλ€.
μμ ν€λ:
Referrer-Policy: strict-origin-when-cross-origin
μΌλ°μ μΈ κ°:
no-referrer: Referer ν€λλ₯Ό μ λ 보λ΄μ§ μμ΅λλ€.no-referrer-when-downgrade: TLS(HTTPS)κ° μλ μΆμ²μλ Referer ν€λλ₯Ό 보λ΄μ§ μμ΅λλ€.origin: Referer ν€λμ μΆμ²(μ€ν€λ§, νΈμ€νΈ, ν¬νΈ)λ§ λ³΄λ λλ€.origin-when-cross-origin: κ΅μ°¨ μΆμ² μμ²μλ μΆμ²λ₯Ό 보λ΄κ³ , λμΌ μΆμ² μμ²μλ μ 체 URLμ 보λ λλ€.same-origin: λμΌ μΆμ² μμ²μλ Referer ν€λλ₯Ό 보λ΄μ§λ§, κ΅μ°¨ μΆμ² μμ²μλ 보λ΄μ§ μμ΅λλ€.strict-origin: νλ‘ν μ½ λ³΄μ μμ€μ΄ λμΌνκ² μ μ§λ λ(HTTPSμμ HTTPSλ‘)λ§ μΆμ²λ₯Ό 보λ΄κ³ , λ μμ ν λμ(HTTPSμμ HTTPλ‘)μΌλ‘λ ν€λλ₯Ό 보λ΄μ§ μμ΅λλ€.strict-origin-when-cross-origin: λμΌ μΆμ² μμ²μ μνν λ μΆμ²λ₯Ό 보λ λλ€. κ΅μ°¨ μΆμ² μμ²μ κ²½μ°, νλ‘ν μ½ λ³΄μ μμ€μ΄ λμΌνκ² μ μ§λ λ(HTTPSμμ HTTPSλ‘)λ§ μΆμ²λ₯Ό 보λ΄κ³ , λ μμ ν λμ(HTTPSμμ HTTPλ‘)μΌλ‘λ ν€λλ₯Ό 보λ΄μ§ μμ΅λλ€.unsafe-url: μΆμ²μ κ΄κ³μμ΄ Referer ν€λμ μ 체 URLμ 보λ λλ€. λ―Όκ°ν μ λ³΄κ° λ ΈμΆλ μ μμΌλ―λ‘ κ·Ήλμ μ£Όμλ₯Ό κΈ°μΈμ¬ μ¬μ©νμμμ€.
Permissions-Policy (μ΄μ Feature-Policy)
Permissions-Policy ν€λ(μ΄μ μλ Feature-Policyλ‘ μλ €μ§)λ κ°λ°μκ° λΈλΌμ°μ κΈ°λ₯ λ° APIλ₯Ό μ νμ μΌλ‘ νμ±ν λ° λΉνμ±νν μ μλλ‘ ν©λλ€. μ΄λ μ ν리μΌμ΄μ
μ 곡격 νλ©΄μ μ€μ΄κ³ μ¬μ©μ κ°μΈ μ 보λ₯Ό ν₯μμν€λ λ° λμμ΄ λ μ μμ΅λλ€.
μμ ν€λ:
Permissions-Policy: geolocation=()
μ΄ μμ λ μΉμ¬μ΄νΈμ μμΉ μ 보 APIλ₯Ό λΉνμ±νν©λλ€.
Permissions-Policyλ‘ μ μ΄ν μ μλ λ€λ₯Έ κΈ°λ₯μ λ€μκ³Ό κ°μ΅λλ€:
cameramicrophonegeolocationaccelerometergyroscopemagnetometerusbmidipaymentfullscreen
λ€μν νλ«νΌμμ 보μ ν€λ μ€μ νκΈ°
보μ ν€λλ₯Ό μ€μ νλ λ°©λ²μ μ¬μ© μ€μΈ μΉ μλ²λ νλ«νΌμ λ°λΌ λ€λ¦ λλ€. λͺ κ°μ§ μΌλ°μ μΈ μλ λ€μκ³Ό κ°μ΅λλ€:
Apache
Apacheμμλ .htaccess νμΌμ΄λ μλ² κ΅¬μ± νμΌ(httpd.conf)μ 보μ ν€λλ₯Ό μΆκ°νμ¬ μ€μ ν μ μμ΅λλ€.
.htaccess κ΅¬μ± μμ :
<IfModule mod_headers.c>
Header set Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; report-uri /csp-report"
Header set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header set X-Frame-Options "DENY"
Header set X-XSS-Protection "1; mode=block"
Header set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>
Nginx
Nginxμμλ Nginx κ΅¬μ± νμΌ(nginx.conf)μ μλ² λΈλ‘μ 보μ ν€λλ₯Ό μΆκ°νμ¬ μ€μ ν μ μμ΅λλ€.
Nginx κ΅¬μ± μμ :
server {
listen 443 ssl;
server_name example.com;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; report-uri /csp-report";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options "DENY";
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy "strict-origin-when-cross-origin";
...
}
Node.js (Express)
Node.jsμμλ Helmetκ³Ό κ°μ λ―Έλ€μ¨μ΄λ₯Ό μ¬μ©νμ¬ λ³΄μ ν€λλ₯Ό μ€μ ν μ μμ΅λλ€.
Helmet μ¬μ© μμ :
const express = require('express');
const helmet = require('helmet');
const app = express();
app.use(helmet());
// νμν κ²½μ° CSP μ¬μ©μ μ μ
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "https://cdn.example.com"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", "data:"],
reportUri: '/csp-report'
},
}));
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server listening on port 3000');
});
Cloudflare
Cloudflareμμλ νμ΄μ§ κ·μΉ(Page Rules) λλ λ³ν κ·μΉ(Transform Rules)μ μ¬μ©νμ¬ λ³΄μ ν€λλ₯Ό μ€μ ν μ μμ΅λλ€.
보μ ν€λ ν μ€νΈνκΈ°
보μ ν€λλ₯Ό ꡬνν νμλ μ¬λ°λ₯΄κ² μλνλμ§ ν μ€νΈνλ κ²μ΄ μ€μν©λλ€. μΉμ¬μ΄νΈμ 보μ ν€λλ₯Ό λΆμνλ λ° λμμ΄ λλ μ¬λ¬ μ¨λΌμΈ λκ΅¬κ° μμ΅λλ€:
- SecurityHeaders.com: 보μ ν€λλ₯Ό λΆμνλ κ°λ¨νκ³ ν¨κ³Όμ μΈ λꡬμ λλ€.
- Mozilla Observatory: 보μ ν€λλ₯Ό ν¬ν¨νμ¬ μΉμ¬μ΄νΈ 보μμ ν μ€νΈνλ ν¬κ΄μ μΈ λꡬμ λλ€.
- WebPageTest.org: μν°ν΄ μ°¨νΈμμ HTTP ν€λλ₯Ό λ³Ό μ μμ΅λλ€.
κ²°λ‘
νλ‘ νΈμλ 보μ ν€λ, νΉν μ½ν μΈ λ³΄μ μ μ± (CSP)μ λ€μν 곡격μΌλ‘λΆν° μΉ μ ν리μΌμ΄μ μ 보νΈνκ³ μ¬μ©μ 보μμ κ°ννλ λ° νμμ μ λλ€. μ΄λ¬ν ν€λλ₯Ό μ μ€νκ² κ΅¬ννκ³ μ μ§ κ΄λ¦¬ν¨μΌλ‘μ¨ XSS, ν΄λ¦μ¬νΉ λ° κΈ°ν 보μ μ·¨μ½μ μ μνμ ν¬κ² μ€μΌ μ μμ΅λλ€. λ³΄κ³ μ μ© μ μ± μΌλ‘ μμνμ¬ μλ° λ³΄κ³ μλ₯Ό λΆμνκ³ μ μ± μ ꡬ체νν λ€μ κ°μ λͺ¨λλ‘ μ ννλ κ²μ κΈ°μ΅νμμμ€. μΉμ¬μ΄νΈκ° λ°μ νκ³ μλ‘μ΄ μνμ΄ λνλ¨μ λ°λΌ 보μ ν€λλ₯Ό μ κΈ°μ μΌλ‘ λͺ¨λν°λ§νκ³ μ λ°μ΄νΈνμ¬ μΉμ¬μ΄νΈλ₯Ό μμ νκ² μ μ§νμμμ€.
νλ‘ νΈμλ 보μμ λν μ¬μ μλ°©μ μ κ·Ό λ°©μμ μ±νν¨μΌλ‘μ¨ μ¬μ©μμ λΉμ¦λμ€λ₯Ό 보νΈνλ λ μμ νκ³ μ λ’°ν μ μλ μΉ μ ν리μΌμ΄μ μ ꡬμΆν μ μμ΅λλ€.